package com.jsbs.iam.user.service.impl;

import com.jsbs.iam.common.core.dto.Result;
import com.jsbs.iam.common.core.utils.Json;
import com.jsbs.iam.common.core.utils.StringUtils;
import com.jsbs.iam.common.redis.service.RedisUtils;
import com.jsbs.iam.user.constant.RedisConstants;
import com.jsbs.iam.user.dto.*;
import com.jsbs.iam.user.entity.IamApplySystem;
import com.jsbs.iam.user.feign.ApplySystemFeign;
import com.jsbs.iam.user.mapper.AdminRoleMapper;
import com.jsbs.iam.user.mapper.ApplyConfigMapper;
import com.jsbs.iam.user.mapper.MenuManagerMapper;
import com.jsbs.iam.user.service.MenuManagerService;
import com.jsbs.iam.user.vo.ButtonInfoVo;
import com.jsbs.iam.user.vo.IamAdminRoleApplyInfoVo;
import com.jsbs.iam.user.vo.MenuManagerInfoVo;
import com.jsbs.iam.user.vo.MenuThreeVo;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @BelongsProject: iam
 * @BelongsPackage: com.jsbs.iam.user.service.impl
 * @Author: WangJiYuan
 * @Description: TODO
 * @Version: 2023-01-04  18:39
 */
@Service
@Slf4j
public class MenuManagerServiceImpl implements MenuManagerService {

    private static final Logger logger = LoggerFactory.getLogger(MenuManagerServiceImpl.class);

    @Autowired
    private MenuManagerMapper menuManagerMapper;

    @Resource
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ApplySystemFeign applySystemFeign;

    @Autowired
    private AdminRoleMapper adminRoleMapper;

    @Autowired
    private ApplyConfigMapper applyConfigMapper;

    @Value("${_env.name}")
    private String envName;

    private static final String ADMIN_USER = "admin";

    @Override
    public List<MenuThreeVo> getMenuThreeInfo(MenuManagerSelectDto menuManagerSelectDto) {

        // 是否包含按钮
        String buttonFlag = menuManagerSelectDto.isIncludeButton() ? RedisConstants.APPLY_MENU_BUTTON : StringUtils.EMPTY;
        // 先从缓存获取菜单数据, 取不到查库，并重新入缓存
        List<MenuThreeVo> menuThreeVos =
                RedisUtils.getObjectList(envName + menuManagerSelectDto.getApplyCode() + RedisConstants.APPLY_MENU + buttonFlag, MenuThreeVo.class);

        // 缓存存在直接返回
        if (CollectionUtils.isNotEmpty(menuThreeVos)) {
            return menuThreeVos;
        }
        // 获取该应用下的所有菜单
        List<MenuThreeVo> menuThrees = menuManagerMapper.selectMenuInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode());
        //判断是否需要含有按钮数据
        if (menuManagerSelectDto.isIncludeButton()) {
            //获取应用下所有按钮数据
            menuThrees.addAll(menuManagerMapper.selectButtonInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode()));
        }
        // 判空
        if (CollectionUtils.isEmpty(menuThrees)) {
            //如果没有菜单数据返回应用数据
            List<MenuThreeVo> applyInfos = menuManagerMapper.selectApplyInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode());
            RedisUtils.setObjectList(envName + menuManagerSelectDto.getApplyCode() + RedisConstants.APPLY_MENU + buttonFlag, applyInfos,12, TimeUnit.HOURS);
            return applyInfos;
        }
        // 获取所有一级组织层级
        menuThreeVos = menuThrees.stream().filter(item -> Objects.nonNull(item.getMenuLevel()) && 1 == item.getMenuLevel()).collect(Collectors.toList());
        // 循环封装层级
        recursionMenuData(menuThreeVos, menuThrees);

        // 获取应用数据
        List<MenuThreeVo> applyInfos = menuManagerMapper.selectApplyInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode());
        for (MenuThreeVo applyInfo : applyInfos) {
            applyInfo.setChildrenMenus(menuThreeVos);
        }
        //把组装了应用数据赋值给menuThreeVos
        menuThreeVos = applyInfos;

        if (CollectionUtils.isEmpty(menuThreeVos)) {
            return null;
        }
        // 最新菜单列表入缓存，失效时间12小时
        RedisUtils.setObjectList(envName + menuManagerSelectDto.getApplyCode() + RedisConstants.APPLY_MENU + buttonFlag, menuThreeVos,12, TimeUnit.HOURS);
        return menuThreeVos;
    }

//    @Override
//    public List<MenuThreeVo> getMenuThreeInfo(MenuManagerSelectDto menuManagerSelectDto) {
//        List<MenuThreeVo> menuThreeVos=new ArrayList<MenuThreeVo>();
//        // 获取该应用下的所有菜单
//        List<MenuThreeVo> menuThrees = menuManagerMapper.selectMenuInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode());
//        //判断是否需要含有按钮数据
//        if (menuManagerSelectDto.isIncludeButton()) {
//            //获取应用下所有按钮数据
//            menuThrees.addAll(menuManagerMapper.selectButtonInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode()));
//        }
//        // 判空
//        if (CollectionUtils.isEmpty(menuThrees)) {
//            return null;
//        }
//        // 获取所有一级菜单，根据菜单编码排序，并转换成vo
//        //menuVoList = menuList.stream().filter(item -> "1".equalsIgnoreCase(item.getMenuLevel())).map(this::beanCopys).sorted(Comparator.comparing(IamMenuVo::getMenuCode)).collect(Collectors.toList());
//        // 获取所有一级组织层级
//        menuThreeVos = menuThrees.stream().filter(item -> Objects.nonNull(item.getMenuLevel()) && 1 == item.getMenuLevel()).collect(Collectors.toList());
//        // 循环封装层级
//        recursionMenuData(menuThreeVos, menuThrees);
//
//        // 获取应用数据
//        List<MenuThreeVo> applyInfos = menuManagerMapper.selectApplyInfo(menuManagerSelectDto.getApplyCode(), menuManagerSelectDto.getCompanyCode());
//        for (MenuThreeVo applyInfo : applyInfos) {
//            applyInfo.setChildrenMenus(menuThreeVos);
//        }
//        //把组装了应用数据赋值给menuThreeVos
//        menuThreeVos = applyInfos;
//
//        if (CollectionUtils.isEmpty(menuThreeVos)) {
//            return null;
//        }
//        return menuThreeVos;
//    }

    @Override
    public int verifyMenuName(String menuSuperiorCode, String menuName, String companyCode) {
        return menuManagerMapper.verifyMenuName(menuSuperiorCode, menuName,companyCode);
    }

    @Override
    public void addMenuInfo(MenuManagerAddDto menuAddDto) {
        //新增菜单生成编码时加锁,失效时间10分钟
        RLock lock = redissonClient.getLock(RedisConstants.MENU_CODE_ADD_KEY);
        String menuCode = "";
        try {
            // 5分钟内重试获取锁，10秒后释放
            boolean isLocked = lock.tryLock(5 * 60 * 1000L, 10 * 1000L, TimeUnit.MILLISECONDS);
            // 获取到锁后,进行菜单编码自增
            if (!isLocked) {
                log.warn("Route=>MenuManagerServiceImpl method=>addMenuInfo 新增菜单加锁生成菜单编码失败:{}", Json.toJsonString(menuAddDto));
                throw new Exception();
            }
            //key 拼接规则 应用编码_当前菜单编码
            String key = "";
            if (StringUtils.isNotEmpty(menuAddDto.getApplyCode())) {
                if (StringUtils.isEmpty(menuAddDto.getMenuCode())) {
                    //添加第一级菜单
                    key = menuAddDto.getApplyCode();
                } else {
                    //添加第一级菜单以下菜单
                    key = menuAddDto.getApplyCode() + "_" + menuAddDto.getMenuCode();
                }
            }
            // 加锁成功后获取当前缓存值
            String value = RedisUtils.get(envName + key);
            // 若为空则从数据库取当前菜单的下一层最大编码,并加入到缓存中
            if (StringUtils.isEmpty(value)) {
                //设置层级 用于查询子级
                menuAddDto.setMenuLevel(menuAddDto.getMenuLevel() + 1);
                String subMenuCode = "";
                //根据不同的层级查询菜单下级编码
                if (menuAddDto.getMenuLevel() == 1) {
                    //添加一级菜单  通过应用类型编码查询一级菜单编码
                    subMenuCode = menuManagerMapper.getSubMenuCodeByApplyCode(menuAddDto);
                }else {
                    //添加一级以后的菜单  通过菜单父级编码查询下级菜单编码
                    subMenuCode = menuManagerMapper.getSubMenuCodeBySuperCode(menuAddDto);
                }
                if (StringUtils.isEmpty(subMenuCode)) {
                    //初始化下级菜单编码
                    if (menuAddDto.getMenuLevel() == 1) {
                        //子级OrgLevel等于1 为应用添加第一级菜单 code 第一级菜单逻辑编码01  第一级菜单真实编码yybm00000101
                        menuCode = menuAddDto.getApplyCode() + "01";
                    } else if (menuAddDto.getMenuLevel() >= 2) {
                        //子级MenuLevel等于2 为第一层组织添加第二层组织 code 第二级菜单逻辑编码0101  第二级菜单真实编码yybm0000010101
                        menuCode = menuAddDto.getMenuCode() + "01";
                    }
                } else {
                    String num = subMenuCode.replace(menuAddDto.getMenuCode(), "");
                    int numInt = Integer.parseInt(num);
                    numInt++;
                    //控制补0的位数
                    int place = 2;
                    menuCode = menuAddDto.getMenuCode() + String.format("%0" + place + "d", numInt);//后缀不够place长，前面补充0
                }
            } else {
                // 当前缓存获取成功
                String num = "";
                //取出后两位数字
                num = value.substring(value.length() - 2);
                int numInt = Integer.parseInt(num);
                numInt++;
                int place = 2;
                String prefix = (StringUtils.isEmpty(menuAddDto.getMenuCode())) ? menuAddDto.getApplyCode() : menuAddDto.getMenuCode();

                menuCode = prefix + String.format("%0" + place + "d", numInt);//后缀不够place长，前面补充0
                //设置层级
                menuAddDto.setMenuLevel(menuAddDto.getMenuLevel() + 1);
            }

            // 将最新值更新到缓存中,过期时间24小时
            RedisUtils.set(envName + key, menuCode, 24 * 60 * 60 * 1000L, TimeUnit.MILLISECONDS);
            log.info("新增菜单生成菜单编码redis===>key: {}", key);
            log.info("新增菜单生成菜单编码redis===>value: {}", menuCode);
        } catch (Exception e) {
            log.error("Route=>MenuManagerServiceImpl method=>addMenuInfo 新增菜单加锁生成菜单编码失败:{}, e:{}", Json.toJsonString(menuAddDto), e);
            e.printStackTrace();
            throw new RuntimeException("新增菜单生成菜单编码异常");
        } finally {
            // 手动解锁
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
        //设置父级编码
        //menuAddDto.setMenuSuperiorCode(StringUtils.isEmpty(menuAddDto.getMenuCode()) ? "0" : menuAddDto.getMenuCode());
        menuAddDto.setMenuSuperiorCode(menuAddDto.getMenuLevel() == 1 ? "0" : menuAddDto.getMenuCode());
        //设置菜单编码
        menuAddDto.setMenuCode(menuCode);

        //新增菜单
        menuManagerMapper.addMenuInfo(menuAddDto);

        //更新Redis缓存
        this.updateRedisKey(menuAddDto.getApplyCode());

    }

    @Override
    @Transactional
    public void deleteMenuInfo(MenuManagerDeleteDto menuDeleteDto) {
        //获取所有下级菜单编码
        List<String> codeData = this.recursionCodeData(menuDeleteDto.getMenuCode(),menuDeleteDto.getCompanyCode());
        codeData.add(menuDeleteDto.getMenuCode());
        menuDeleteDto.setApplyCodeData(codeData);
        //获取所有按钮编码集合
        List<String> buttonCodeList = menuManagerMapper.getButtonCodes(menuDeleteDto);
        menuDeleteDto.setButtonCodes(buttonCodeList);
        //通过菜单编码逻辑删除菜单数据
        menuManagerMapper.deleteMenuInfo(menuDeleteDto);
        //通过菜单编码删除菜单对应按钮数据
        menuManagerMapper.deleteButtonInfo(menuDeleteDto);

        //删除菜单,按钮和角色,用户组的关联关系
        Result result = applySystemFeign.deleteMenuRelation(menuDeleteDto);
        log.info("远程调用auth服务,删除菜单,按钮和角色,用户组的关联关系", Json.toJsonString(result));
        if (!result.isSuccess()) {
            log.error("远程调用auth服务,删除菜单,按钮和角色,用户组的关联关系失败!!!", Json.toJsonString(result));
            throw new RuntimeException("远程调用auth服务,删除菜单,按钮和角色,用户组的关联关系失败!!!");
        }

        //更新Redis缓存
        this.updateRedisKey(menuDeleteDto.getApplyCode());
    }

    @Override
    public MenuManagerInfoVo selectMenuData(String menuCode, String companyCode) {
        return menuManagerMapper.selectMenuData(menuCode,companyCode);
    }

    @Override
    public String getMenuName(String menuCode, String companyCode) {
        return menuManagerMapper.getMenuName(menuCode,companyCode);
    }

    @Override
    public void updateMenuInfo(MenuManagerAddDto menuAddDto) {
        menuManagerMapper.updateMenuInfo(menuAddDto);
        //更新Redis缓存
        this.updateRedisKey(menuAddDto.getApplyCode());
    }

    @Override
    public int verifyMenuLevel(String menuCode) {
        return menuManagerMapper.verifyMenuLevel(menuCode);
    }

    @Override
    public void moveMenu(MenuManagerAddDto menuAddDto) {
        //修改菜单之间的子父级关系
        menuManagerMapper.updateMenuLevel(menuAddDto);

        //更新Redis缓存
        this.updateRedisKey(menuAddDto.getApplyCode());
    }

    @Override
    public List<ButtonInfoVo> selectButtonData(String menuCode, String companyCode) {
        return menuManagerMapper.selectButtonData(menuCode,companyCode);
    }

    @Override
    public int verifyButtonName(String menuCode, String buttonName, String companyCode) {
        return menuManagerMapper.verifyButtonName(menuCode, buttonName,companyCode);
    }

    @Override
    public void addButtonInfo(ButtonAddDto buttonAddDto) {
        //系统自动生成按钮编码
        buttonAddDto.setButtonCode(this.getButtonCodeByRedis("an", 6, buttonAddDto.getCompanyCode()));
        menuManagerMapper.addButtonInfo(buttonAddDto);
    }

    @Override
    public String getButtonName(String buttonCode, String companyCode) {
        return menuManagerMapper.getButtonName(buttonCode,companyCode);
    }

    @Override
    public void updateButtonInfo(ButtonAddDto buttonAddDto) {
        menuManagerMapper.updateButtonInfo(buttonAddDto);
    }

    @Override
    @Transactional
    public void deleteButtonData(ButtonDeleteDto buttonDeleteDto) {
        //删除按钮数据
        menuManagerMapper.deleteButtonData(buttonDeleteDto);
        //删除按钮和角色,用户组的关联关系
        Result result = applySystemFeign.deleteButtonRelation(buttonDeleteDto);
        log.info("远程调用auth服务,删除按钮和角色,用户组的关联关系", Json.toJsonString(result));
        if (!result.isSuccess()) {
            log.error("远程调用auth服务,删除按钮和角色,用户组的关联关系失败!!!", Json.toJsonString(result));
            throw new RuntimeException("远程调用auth服务,删除按钮和角色,用户组的关联关系失败!!!");
        }
    }

    @Override
    public void updateButtonStatus(ButtonStatusUpdateDto buttonStatusUpdateDto) {
        menuManagerMapper.updateButtonStatus(buttonStatusUpdateDto);
    }

    @Override
    public List<IamApplySystem> getApplyData(Map<String, String> params) {
        List<IamApplySystem> applyData = new ArrayList<IamApplySystem>();
        if (ADMIN_USER.equals(params.get("userCode"))) {
            //超管
            applyData = menuManagerMapper.getApplyData(params.get("companyCode"));
        } else {
            //获取角色应用权限
            List<IamAdminRoleApplyInfoVo> adminRoleApplyInfo = adminRoleMapper.getAdminRoleApplyInfo(params.get("adminRoleCode"), params.get("companyCode"));
            //角色权限下应用编码
            List<String> adminRoleApplyCodes = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(adminRoleApplyInfo)) {
                if (adminRoleApplyInfo.get(0).getType() == 1) {
                    //取出所有应用编码
                    for (IamAdminRoleApplyInfoVo iamAdminRoleApplyInfo : adminRoleApplyInfo) {
                        adminRoleApplyCodes.add(iamAdminRoleApplyInfo.getApplyId().toString());
                    }
                } else {
                    //取出应用类型
                    String applyTypeCode = adminRoleApplyInfo.get(0).getApplyTypeCode();
                    //通过应用类型编码查询应用编码
                    List<String> applyCodes = applyConfigMapper.getApplyCodes(applyTypeCode, params.get("companyCode"));
                    adminRoleApplyCodes.addAll(applyCodes);
                }
                //根据应用编码查询应用数据
                applyData = menuManagerMapper.getAdminRoleApplyData(adminRoleApplyCodes,params.get("companyCode"));
            }
        }
        return applyData;
    }

    /**
     * 通过编码获取下级菜单编码
     *
     * @param code
     * @param companyCode
     * @return
     */
    private List<String> recursionCodeData(String code, String companyCode) {
        List<String> recursionList = new ArrayList<String>();
        if (StringUtils.isNotEmpty(code)) {
            List<String> subCode2 = menuManagerMapper.selectSubMenuCode(code,companyCode);
            if (CollectionUtils.isNotEmpty(subCode2)) {
                for (String s : subCode2) {
                    if (StringUtils.isNotEmpty(s)) {
                        List<String> codeData = this.recursionCodeData(s, companyCode);
                        recursionList.addAll(codeData);
                    }
                }
            }
            recursionList.addAll(subCode2);
        }
        return recursionList;
    }

    private void updateRedisKey(String applyCode) {
        RedisUtils.del(envName + applyCode + RedisConstants.APPLY_MENU + StringUtils.EMPTY);
    }

    /**
     * 递归封装子级数据
     * @param menuThreeVos
     * @param menuThrees
     */
    private void recursionMenuData(List<MenuThreeVo> menuThreeVos, List<MenuThreeVo> menuThrees) {
        // 循环封装
        menuThreeVos.forEach(item -> {
            List<MenuThreeVo> children = new ArrayList<>();
            for (MenuThreeVo menuThree : menuThrees) {
                // 获取当前层级的子级
                if (StringUtils.isNotEmpty(menuThree.getParentCode()) && menuThree.getParentCode().equals(item.getCode())) {
                    children.add(menuThree);
                }
                // 递归封装子级的子级
                recursionMenuData(children, menuThrees);
                // 排序
                children.sort(Comparator.comparing(MenuThreeVo::getMenuIndex));
            }
            // 当前层级的所有子级
            item.setChildrenMenus(children);
        });
    }

    /**
     * 生成按钮编码
     * @param prefix
     * @param numLength
     * @param companyCode
     * @return
     */
    public String getButtonCodeByRedis(String prefix, int numLength, String companyCode) {
        // 生成按钮编码加锁，失效时间10分钟
        RLock lock = redissonClient.getLock(RedisConstants.BUTTON_CODE_LOCK);
        String returnCode="";
        try {
            String upperCode ="";
            //Redis中存储的key
            String key = "button_" + prefix;
            Long size = redisTemplate.opsForList().size(key);//查找 prefix 的key值的数据长度
            if (size > 0) {//有数据
                List leve = redisTemplate.opsForList().range(key, 0, -1);//获取该key下面的所有值(-1 所有的值，；1下一个值
                upperCode = leve.get(leve.size() - 1).toString();//返回最后一个值
            }
            //判断redis中序列是否为空
            if (org.apache.commons.lang3.StringUtils.isEmpty(upperCode)) {
                //获取按钮
                upperCode = menuManagerMapper.selectButtonCode(companyCode);
            }
            int Suffix;  //后缀数字
            if (!StringUtil.isNullOrEmpty(upperCode)) { //有数据
                String sequence = upperCode.substring(prefix.length());//截取前缀开始的后面的数字
                Suffix = Integer.parseInt(sequence);
                Suffix++;//最后的序号加一
            } else {
                Suffix = 1;//没有数据
            }
            returnCode = prefix + String.format("%0" + numLength + "d", Suffix);//后缀不够numLength长，前面补充0
            redisTemplate.opsForList().rightPush(key, returnCode);//存入Redis
        } catch (Exception e) {
            log.error("Route=>MenuManagerServiceImpl method=>getButtonCodeByRedis 生成按钮编码,系统自动生成编码失败:", e);
            throw new RuntimeException("生成按钮编码,系统自动生成编码异常");
        } finally {
            // 手动解锁
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
        return  returnCode;
    }

}
